package com.kan.awaits;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import com.kan.awaits.exception.DBAccessException;
import com.kan.awaits.validate.Validator;

/**
 * EOROR 把查询语句和语句的重构到外面去,1处理不同数据库对查询的不同;2,防止同一个Criteria被使用多次出现的错误
 * 
 * @author Elva
 * 
 * @param <T>
 */
public class Criteria<T>
{
	List<Restriction> restrictions = new Vector<Restriction>();

	List<Order> orders = new Vector<Order>();

	List<Object> values = new Vector<Object>();

	List<Object> countValues = new Vector<Object>();

	int firstResult = -1;//

	int maxResults = -1;// <=0 RP NO RES

	@Deprecated
	private Criteria()
	{

	}

	protected List<Object> getValues()
	{
		return values;
	}

	public Criteria<T> add(Restriction restriction)
	{
		restrictions.add(restriction);
		return this;
	}

	public Criteria<T> addOrder(Order order)
	{
		orders.add(order);
		return this;
	}

	public Criteria<T> setFirstResult(int firstResult)
	{
		this.firstResult = firstResult;
		return this;
	}

	public Criteria<T> setMaxResults(int maxResults)
	{
		this.maxResults = maxResults;
		return this;
	}

	final static String SELECT = "SELECT ";
	final static String FROM = " FROM ";
	private String tableName = "TABLE_NAME";

	public String getTableName()
	{
		return tableName;
	}

	final static String WHERE = " WHERE ";

	final static String ORDER_BY = " ORDER BY ";

	final static String LIMIT = " LIMIT ";

	protected String buildCountSql()
	{
		StringBuffer sb = new StringBuffer();
		sb.append(SELECT);
		sb.append("COUNT(*)");
		sb.append(SELECT);
		sb.append(tableName);
		if (Validator.notEmpty(restrictions))
		{
			sb.append(WHERE);
			boolean firstFlg = true;
			for (Restriction restriction : restrictions)
			{
				if (firstFlg)
				{
					sb.append(restriction.getSql());
					firstFlg = false;
				} else
				{
					sb.append(" AND ").append(restriction.getSql());
				}
				countValues.addAll(Arrays.asList(restriction.getValues()));
			}
		}
		return sb.toString();
	}

	protected String buildSql()
	{
		StringBuffer sb = new StringBuffer();
		sb.append(SELECT);
		sb.append("*");
		sb.append(SELECT);
		sb.append(tableName);

		if (Validator.notEmpty(restrictions))
		{
			sb.append(WHERE);
			boolean firstFlg = true;
			for (Restriction restriction : restrictions)
			{
				if (firstFlg)
				{
					sb.append(restriction.getSql());
					firstFlg = false;
				} else
				{
					sb.append(" AND ").append(restriction.getSql());
				}
				values.addAll(Arrays.asList(restriction.getValues()));
			}
		}
		if (Validator.notEmpty(orders))
		{
			sb.append(ORDER_BY);
			boolean firstFlg2 = true;
			for (Order order : orders)
			{
				if (firstFlg2)
				{
					sb.append(order.getSubSql());
					firstFlg2 = false;
				} else
				{
					sb.append(" , ").append(order.getSubSql());
				}
			}
		}
		if (firstResult >= 0 && maxResults >= 0)// firstResult && maxResults
		{
			sb.append(LIMIT).append("?").append(" , ").append("?");
			values.add(firstResult);
			values.add(maxResults);
		} else if (firstResult < 0 && maxResults >= 0)// maxResults ONLY
		{
			sb.append(LIMIT).append("?");
			values.add(maxResults);
		} else if (firstResult >= 0 && maxResults < 0)// firstResult ONLY
		{
			sb.append(LIMIT).append("?").append(" , ").append(Long.MAX_VALUE);
			values.add(firstResult);
		}
		// else DO NOT APPEND LIMIT CLAUSE
		return sb.toString();
	}

	private Class<T> clazz;

	private Connection connection;

	public static <T> Criteria<T> get(Class<T> t, Connection connection, HashMap<Class<?>, TableMeta<?>> tableMetas)
	{
		Criteria<T> criteria = new Criteria<T>();
		criteria.connection = connection;
		criteria.clazz = t;
		criteria.tableMetas = tableMetas;
		return criteria;
	}

	private HashMap<Class<?>, TableMeta<?>> tableMetas;

	public int count() throws DBAccessException
	{
		final TableMeta<T> tableMeta = (TableMeta<T>) tableMetas.get(clazz);
		if (tableMeta == null)
		{
			throw new DBAccessException("Can not list " + clazz + " Cause:the type not bean mapped");
		}
		tableName = tableMeta.getTableName();

		String sql = buildCountSql();

		return DBHelper.query(connection, sql, new ResultCallBack<Integer>()
		{
			@Override
			public Integer dealWithResult(ResultSet rs) throws SQLException
			{
				if (rs.next())
				{
					return rs.getInt(1);
				}
				return -1;
			}
		}, getValues().toArray());
	}

	/**
	 * 返回唯一的结果
	 * 
	 * @return 如果没有结果返回NULL,结果唯一返回这个唯一的结果,否则(结果>1)抛出异常
	 * @throws DBAccessException
	 */
	public T uniqueResult() throws DBAccessException
	{
		List<T> list = list();
		if (list.size() > 1)
		{
			throw new DBAccessException("The Result Set is Not Unique!");
		}
		if (list.isEmpty())
		{
			return null;
		}
		return list.get(0);
	}

	/**
	 * 执行查询列出所有查询得到的结果列表
	 * 
	 * @return
	 * @throws DBAccessException
	 */
	public List<T> list() throws DBAccessException
	{
		final TableMeta<T> tableMeta = (TableMeta<T>) tableMetas.get(clazz);
		if (tableMeta == null)
		{
			throw new DBAccessException("Can not list " + clazz + " Cause:the type not bean mapped");
		}
		tableName = tableMeta.getTableName();
		String sql = buildSql();

		return DBHelper.query(connection, sql, new ResultCallBack<List<T>>()
		{
			@Override
			public List<T> dealWithResult(ResultSet rs) throws SQLException
			{
				return list(tableMeta, rs);
			}
		}, getValues().toArray());

	}

	protected static <T> List<T> list(TableMeta<T> tableElement, ResultSet rs)
	{
		Set<ColumnMeta> mts = tableElement.getColumnMetas();
		try
		{
			List<T> list = new ArrayList<T>();
			while (rs.next())
			{
				T a = tableElement.newObject();
				if (a != null)
				{
					for (ColumnMeta mt : mts)
					{
						DBHelper.callSetter(a, mt.getPropertyDescriptor(), rs.getObject(mt.getColumnName()));
					}
					list.add(a);
				}
			}
			return list;
		} catch (SQLException e)
		{
			e.printStackTrace();
			return null;
		} catch (Exception e)
		{
			e.printStackTrace();
			return null;
		}
	}
}
